package fr.cephb.joperon.tool.gen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

import fr.cephb.joperon.core.bio.Assembly;
import fr.cephb.joperon.core.bio.Organism;
import fr.cephb.util.XMLUtilities;

/**
 * Read a Mario's config file as Tab delimited and generates CODE
 * using apache velocity
 *
 */
public class XLS2CodeGeneration
	{
	private static final Pattern TAB=Pattern.compile("[\t]");
	
	
	private XLS2CodeGeneration() throws Exception
		{
		
	  }
	
	/**
	 * 
	 * Base
	 *
	 */
	protected static class Base
		{
		String name=null;
		String description=null;
		protected Base()
			{
			
			}
		
		public String getXmlName()
			{
			return getName();
			}
		
		public String getName() {
			return name;
			}
		public String getDescription() {
			return description==null?getName():this.description;
			}
		public String getJavaName()
			{
			return getName().substring(0,1).toUpperCase()+ getName().substring(1);
			}
		}
	
	/**
	 * Table
	 * @author lindenb
	 *
	 */
	public static class Table
		extends Base
		{
		int physical_location;
		String domain;
		String accessMethod=null;
		int duplicateKey=-1;
		Field key=null;
		List<Field> fields= new ArrayList<Field>();
		List<SecondaryDB> secondary= new ArrayList<SecondaryDB>();
		boolean inMemory=false;
		public List<SecondaryDB> getSecondary()
			{
			return this.secondary;
			}
		
		@Override
		public String getName() {
			String s=super.getName();
			return s.substring(0,1).toUpperCase()+s.substring(1);
			}
		
		public String getDatabaseType()
			{
			return getAccess().substring(3);
			}
		
		public boolean isInMemory() {
			return inMemory;
			}
		
		public String getAccess()
			{
			if(this.accessMethod.equals("BT"))
				{
				return "DB_BTREE";
				}
			else if(this.accessMethod.equals("H"))
				{
				return "DB_HASH";
				}
			else if(this.accessMethod.equals("Q"))
				{
				return "DB_QUEUE";
				}
			else if(this.accessMethod.equals("R"))
				{
				return "DB_RECNO";
				}
			else throw new IllegalArgumentException();
			}
		
		public List<Field> getFields()
			{
			return this.fields;
			}
		public Field getKey()
			{
			return this.key;
			}
		
		public boolean isAllowingDuplicates()
			{
			return this.duplicateKey==1;
			}
		
		public String getDomain() {
			return domain;
			}
		
		public boolean isAssemblyDependent()
			{
			return this.physical_location==1;
			}
		
		public String getTypedef()
			{
			return getName()+"_t";
			}
		public String getPtr()
			{
			return getName()+"Ptr";
			}
		
		public Field getFidField()
			{
			for(Field f: getFields())
				{
				if(f.isFid()) return f;
				}
			return null;	
			}

		public boolean isFidDabatabase()
			{
			return getKey().isFid();
			}
		
		public boolean isSimpleMap()
			{
			return getFields().size()==1;
			}
		
		public Field getSingleField()
			{
			if(isSimpleMap())
				{
				return getFields().get(0);
				}
			return null;
			}
		
		public boolean hasFid()
			{
			return getFidField()!=null;			
			}
		public boolean hasFieldWithByteAligned()
			{
			for(Field f: getFields())
				{
				if(f.isByteAligned()) return true;
				}
			return false;	
			}
		
		public int getSizeOf()
			{
			int len=0;
			if(hasFieldWithByteAligned()) return -1;
			for(Field f: getFields())
				{
				String t= f.getJavaType();
				if(t.equals("long"))
					{
					len+=8;
					}
				else if(t.equals("int"))
					{
					len+=4;
					}
				else if(t.equals("short"))
					{
					len+=2;
					}
				else if(t.equals("byte") ||
						t.equals("boolean"))
					{
					len+=1;
					}
				else if(t.equals("float"))
					{
					len+=4;
					}
				else if(t.equals("double"))
					{
					len+=8;
					}
				else
					{
					return -1;
					}
				}
			return len;
			}
		
		public boolean hasFixedSize()
			{
			return getSizeOf()!=-1;
			}
		
		}
	/** secondary DB */
	public static class SecondaryDB
		extends Base
		{
		Field key;
		
		public Field getKey() {
			return key;
			}
		}
	
	
	/**
	 * 
	 * Field
	 *
	 */
	public static class Field
		extends Base
		{
		String type;
		int expectedSize=0;
		
		
		public int getExpectedSize()
			{
			return this.expectedSize;
			}
		
		/** returns type declared by mario */
		public String getType()
			{
			if(this.type.equals("char *")) return "char*";
			return this.type;
			}
		
		/** returns C++ type */
		public String getCcType()
			{
			String s= getType();
			if(s.equals("char*")) return "std::string";
			return s;
			}
		
		/** returns X-tended C type eg. type= fid => fid_t */
		public String getXType()
			{
			
			if(getName().equalsIgnoreCase("fid") && isString())
				{
				return "fid_t";
				}
			return getType();
			}
		
		public boolean isFid()
			{
			
			return(getXType().equals("fid_t"));
			}
		
		public boolean isString()
			{
			return getType().equals("char*");
			}
		
		public boolean isPointer()
			{
			return getType().endsWith("*") || getType().equals("u_int64_t");
			}
		
		public String getJavaType()
			{
			if(isFid())
				{
				return "fr.cephb.joperon.core.Fid";
				}
			else if(getType().equals("u_int64_t"))
				{
				return "java.math.BigInteger";
				}
			else if(getType().equals("u_int32_t") ||
					getType().equals("int64_t"))
				{
				return "long";
				}
			else if(getType().equals("u_int16_t")||
					getType().equals("int32_t"))
				{
				return "int";
				}
			else if(getType().equals("int16_t") ||
					getType().equals("u_int8_t"))
				{
				return "short";
				}
			else if(getType().equals("int8_t"))
				{
				return "byte";
				}
			else if(getType().equals("float"))
				{
				return "float";
				}
			else if(getType().equals("double"))
				{
				return "double";
				}
			else if(isString())
				{
				return "String";
				}
			throw new IllegalArgumentException(getType());
			}
		
		public String getKnimeCellType()
			{
			String c=getJavaClass();
			if(c.equals("Long"))
				{
				return "fr.cephb.knime.data.def.LongCell";
				}
			else if (c.equals("Short") || c.equals("Integer"))
				{
				return "org.knime.core.data.def.IntCell";
				}
			else if(c.equals("Double") || c.equals("Float"))
				{
				return "org.knime.core.data.def.DoubleCell";
				}
			else if(c.equals("String") || c.equals("fr.cephb.joperon.core.Fid"))
				{
				return "org.knime.core.data.def.StringCell";
				}
			throw new IllegalArgumentException("cannot get knime type for "+c);
			}
		
		public String getKnimeCellValue()
			{
			String c=getKnimeCellType();
				if(c.equals("fr.cephb.knime.data.def.LongCell"))
				{
				return "getLongValue";
				}
			else if(c.equals("org.knime.core.data.def.IntCell"))
				{
				return "getIntValue";
				}
			else if(c.equals("org.knime.core.data.def.DoubleCell"))
				{
				return "getDoubleValue";
				}
			else if(c.equals("org.knime.core.data.def.StringCell"))
				{
				return "getStringValue";
				}
			throw new IllegalArgumentException("cannot get knime type for "+c);
			}
		
		public String getJavaClass()
			{
			if(getType().equals("u_int32_t") ||
					getType().equals("int64_t"))
				{
				return "Long";
				}
			else if(getType().equals("int32_t") ||
					getType().equals("u_int16_t"))
				{
				return "Integer";
				}
			else if(getType().equals("int16_t") ||
					getType().equals("u_int8_t"))
				{
				return "Short";
				}
			else if(getType().equals("int8_t"))
				{
				return "Byte";
				}
			else if(getType().equals("float"))
				{
				return "Float";
				}
			else if(getType().equals("double"))
				{
				return "Double";
				}
			return getJavaType();
			}
		
		public boolean isByteAligned()
			{
			return getName().contains(":");
			}
		}
	
	private List<Table> structures= new ArrayList<Table>();
	
	private void read(File file) throws IOException
		{
		BufferedReader r= new BufferedReader(new FileReader(file));
		String line=r.readLine();
		if(line==null) throw new IOException("Cannot get the hreader");
		String header[]=TAB.split(line);
		if(header.length!=17 && header.length!=18) throw new IOException("Expected 18 columns in the Header "+line+" but found "+header.length);
		Table table=null;
		while((line=r.readLine())!=null)
			{
			if(line.trim().length()==0) continue;
			String tokens[]= TAB.split(line);
			if(table==null || !table.name.equals(tokens[4]))
				{
				if(table!=null) structures.add(table);
				table= new Table();
				table.physical_location= Integer.parseInt(tokens[0]);
				table.inMemory=  Integer.parseInt(tokens[1])==0;
				table.domain= tokens[2].trim();
				table.description =tokens[3].trim();
				table.name=tokens[4].trim();
	
				table.accessMethod= tokens[6].trim();
				table.duplicateKey= Integer.parseInt(tokens[7]);
				if(header.length==18)
					{
					//new format, mario incremented the values by 1
					if(table.physical_location==1)
						{
						table.physical_location=0;
						}
					else if(table.physical_location==2)
						{
						table.physical_location=1;
						}
					else
						{
						throw new IllegalStateException("Boum "+line);
						}
					}
				}
			Field f= new Field();
			int colIndex= tokens[13].trim().length()==0?14:13;
			f.name = tokens[colIndex].trim();
			f.type = tokens[15].trim();
			if(header.length==18)
				{
				
				f.expectedSize= Integer.parseInt(tokens[16].trim());
				f.description = tokens[17].trim();
				}
			else if(header.length==17)
				{
				f.expectedSize= 0;
				f.description = tokens[16].trim();
				}
			else
				{
				throw new IllegalStateException("Boum");
				}
			
			
			if(colIndex==13)
				{
				table.key=f;
				}
			else
				{
				table.fields.add(f);
				}
			
			//secondary db
			if(tokens[5].trim().length()>0)
				{
				SecondaryDB secondaryDB= new SecondaryDB();
				secondaryDB.name= tokens[5].trim();
				secondaryDB.key= f;
				table.secondary.add(secondaryDB);
				}
			
			}
		if(table!=null) structures.add(table);
		r.close();
		}
	
	private void removeWeirdTables()
		{
		Iterator<Table> iter= structures.iterator();
		while(iter.hasNext())
			{
			Table t= iter.next();
			if(t.hasFieldWithByteAligned())
				{
				System.err.println("IGNORING :"+t.getName());
				iter.remove();
				}
			}
		
		}
	
	
	
	private File polymorphismDir()
		{
		return new File((File)null,"..");
		}
	
	private File operonDir()
		{
		return new File(polymorphismDir(),"operon");
		}
	
	private File JOperonDir()
		{
		return new File(polymorphismDir(),"joperon/src/java/fr/cephb/joperon");
		}
	
	private File getPythonDir()
		{
		return new File(polymorphismDir(),"pytoperon");
		}
	
	private File getCppDir()
		{
		return new File(polymorphismDir(),"operoncc");
		}
	
	private void merge(String templateName,VelocityContext context,File f)
	throws Exception
		{
		VelocityEngine engine;
		engine = new VelocityEngine();
		engine.setProperty(VelocityEngine.RESOURCE_LOADER, "class");
		engine.setProperty(
	            "class." + VelocityEngine.RESOURCE_LOADER + ".class",
	            ClasspathResourceLoader.class.getName()
	            );
		/*
		engine.setProperty(VelocityEngine.VM_LIBRARY,
	   		 "/velocity/macros.vm"
	   		 ); */
	
		engine.init();
		
		Template template =   engine.getTemplate("/velocity/"+templateName);
		PrintWriter out= new PrintWriter(f,"UTF-8");
		template.merge(context, out);
		out.flush();
		out.close();
		}
	private VelocityContext newContext() throws Exception
		{
		VelocityContext context =  new VelocityContext();
		context.put( "today", new java.util.Date());
		return context;
		}
	
	
	/**
	 * Generate C code
	 * @param args
	 */
	private void generateCCode() throws Exception
		{
		VelocityContext context=null;
		File coreDir = new File(operonDir(),"src/core");
		context= newContext();
		context.put("structures",this.structures);
		merge("C/structures.h.vm",context,new File(coreDir,"structures.h"));
		
		context= newContext();
		context.put("structures",this.structures);
		merge("C/structures.c.vm",context,new File(coreDir,"structures.c"));
		
		context= newContext();
		context.put("structures",this.structures);
		merge("C/fromCSV.c.vm",context,new File(coreDir,"fromCSV.c"));
		
		context= newContext();
		context.put("structures",this.structures);
		merge("C/toCSV.c.vm",context,new File(coreDir,"toCSV.c"));
		}
	
	/** remove a few structure from the table */
	public List<Table> filteredListOfStructures()
		{
		List<Table> L= new ArrayList<Table>(this.structures);
		int i=0;
		while(i< L.size())
			{
			if(L.get(i).getName().equalsIgnoreCase("gtypes"))
				{
				L.remove(i);
				}
			else
				{
				++i;
				}
			}
		return L;
		}
	
	/**
	 * Generate Python code
	 */
	private void generatePythonCode() throws Exception
		{
		VelocityContext context=null;
		File src= new File(getPythonDir(),"src");
		for(Table struct: this.structures)
			{
			context= newContext();
			context.put("struct",struct);
			merge("PYTHON/entities.py.vm",context,new File(src,"Base"+struct.getName()+".py"));
			}
		}
	/**
	 * Generate CC code
	 */
	private void generateCPPCode() throws Exception
		{
		VelocityContext context=null;
		File src= new File(getCppDir(),"src");
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("CPP/entities.hh.vm",context,new File(src,"entities.h"));
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("CPP/entities.cpp.vm",context,new File(src,"entities.cpp"));
		}	
	
	
	/**
	 * Generate JAVA code
	 */
	private void generateJavaCode() throws Exception
		{
		VelocityContext context=null;
		File coreDir=new File(JOperonDir(),"core");
		coreDir.mkdir();
		File entitiesDir=new File(coreDir,"entities");
		entitiesDir.mkdir();
		File baseEntitiesDir=new File(entitiesDir,"base");
		baseEntitiesDir.mkdir();
		//File knimeDir=new File(JOperonDir(),"knime/plugins");
		File databaseDir=new File(coreDir,"db");
		databaseDir.mkdir();
		File baseDatabaseDir=new File(databaseDir,"base");
		baseDatabaseDir.mkdir();
		
		//generate the protected BaseXXX.java
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/operonfactory.java.vm",context,new File(JOperonDir(),"tool/OperonFactory.java"));
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/TestUnit.java.vm",context,new File(JOperonDir(),"unit/TestDatabases.java"));
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/operonswing.java.vm",context,new File(JOperonDir(),"swing/OperonGUI2.java"));
		
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/xmlfactory.java.vm",context,new File(JOperonDir(),"tool/BaseXMLFactory.java"));
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/JOperonJSON.java.vm",context,new File(JOperonDir(),"webapp/JOperonJSON.java"));
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/RestServlet.java.vm",context,new File(JOperonDir(),"webapp/RestServlet.java"));
		
		
		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/jsonfactory.java.vm",context,new File(JOperonDir(),"tool/JSONFactory.java"));

		context= newContext();
		context.put("structures",filteredListOfStructures());
		merge("Java/oql.jj.vm",context,new File(JOperonDir(),"tool/oql/OperonQueryLanguage.jj"));
		
		
		for(Table struct: this.structures)
			{
			//generate the protected PojoXXX.java
			context= newContext();
			context.put("struct",struct);
			merge("Java/pojo.java.vm",context,new File(baseEntitiesDir,"Pojo"+struct.getName()+".java"));
			
			//generate the protected BaseXXX.java
			context= newContext();
			context.put("struct",struct);
			merge("Java/entity.java.vm",context,new File(baseEntitiesDir,"Base"+struct.getName()+".java"));
			
			//generate the protected database BaseXXX.java
			context= newContext();
			context.put("struct",struct);
			merge("Java/db.java.vm",context,new File(baseDatabaseDir,"Base"+struct.getName()+"DB.java"));
			
			
			//generate the XXX.jaav derived from BaseXXX.java if needed
			File topClass= new File(entitiesDir,struct.getName()+".java");
			if(!topClass.exists())
				{
				context= newContext();
				context.put("struct",struct);
				merge("Java/topclass.java.vm",context,topClass);
				}
			
			
				
			//generate database stuff
			File dbClass= new File(databaseDir,struct.getName()+"DB.java");
			if(!dbClass.exists())
				{
				context= newContext();
				context.put("struct",struct);
				merge("Java/topdb.java.vm",context,dbClass);
				}	
			}
	
		}
	
	
		static private class DiaHandle
			{
			Field f;
			int structIndex=0;
			int fieldIndex=0;
			float x,y,width,height;
			}
	
		private void printFieldAsDia(PrintWriter out,Field f,String prefix) throws IOException
			{
			out.println(
			"        <dia:composite type=\"umlattribute\">" +
			"          <dia:attribute name=\"name\">" +
			"            <dia:string>#"+prefix+URLEncoder.encode(f.getName(),"UTF-8") +"#</dia:string>" +
			"          </dia:attribute>" +
			"          <dia:attribute name=\"type\">" +
			"            <dia:string>#"+prefix+URLEncoder.encode(f.getType(),"UTF-8")+"#</dia:string>" +
			"          </dia:attribute>" +
			"          <dia:attribute name=\"value\">" +
			"            <dia:string>##</dia:string>" +
			"          </dia:attribute>" +
			"          <dia:attribute name=\"comment\">" +
			"            <dia:string>#"+ URLEncoder.encode(f.getDescription(),"UTF-8")+"#</dia:string>" +
			"          </dia:attribute>" +
			"          <dia:attribute name=\"visibility\">" +
			"            <dia:enum val=\"0\"/>" +
			"          </dia:attribute>" +
			"          <dia:attribute name=\"abstract\">" +
			"            <dia:boolean val=\"false\"/>" +
			"          </dia:attribute>" +
			"          <dia:attribute name=\"class_scope\">" +
			"            <dia:boolean val=\"false\"/>" +
			"          </dia:attribute>" +
			"        </dia:composite>"
			);
			}
	
	/** Generate a Diagram for Dot */
	private void generateDot() throws Exception
		{
		File dir =new File(polymorphismDir(),"joperon");
		File docDir =new File(dir,"doc");
		docDir.mkdir();
		File diaDir =new File(docDir,"dot");
		diaDir.mkdir();
		
		
		Set<String> domains= new HashSet<String>();
		for(int i1=0;i1< structures.size();++i1 )
			{	
			domains.add(structures.get(i1).getDomain());
			}
		
		for(String domain: domains)
			{
			Set<Integer> tabeIndexes=new HashSet<Integer>();
			File fout=new File(diaDir,"operon."+domain.toLowerCase()+".dot");
			PrintWriter out= new PrintWriter(new FileWriter(fout));
			out.println("digraph G {");
			for(int i1=0;i1< structures.size();++i1 )
				{
				if(!structures.get(i1).getDomain().equals(domain)) continue;
				Field f1 = structures.get(i1).getKey();
				
				for(int i2=0;i2< structures.size();++i2 )
					{
					if(!structures.get(i2).getDomain().equals(domain)) continue;
					for(Field f2: structures.get(i2).getFields())
						{
						if(!f1.description.equalsIgnoreCase(f2.description)) continue;
						tabeIndexes.add(i1);
						tabeIndexes.add(i2);
						out.println("t"+i2+" -> t"+i1+"[label=\""+ f1.getDescription().replace('"', ' ')+"\"];");
						}
					}
				
				}
			for(int i1: tabeIndexes)
				{
				out.println("t"+i1+"[label=\""+structures.get(i1).getName()+":"+ structures.get(i1).getKey().getName()+"\"];");
				}
			out.println("}");
			out.flush();
			out.close();
			if(tabeIndexes.isEmpty())
				{
				fout.delete();
				}
			}
		}
		
		
	/** Generate a Diagram for DIA */
	private void generateDia() throws Exception
		{
		TreeMap<String,List<DiaHandle>> connections= new TreeMap<String,List<DiaHandle>>(String.CASE_INSENSITIVE_ORDER);
		File dir =new File(polymorphismDir(),"joperon");
		File docDir =new File(dir,"doc");
		docDir.mkdir();
		File diaDir =new File(docDir,"dia");
		diaDir.mkdir();
		PrintWriter out= new PrintWriter(new FileWriter(new File(diaDir,"operon.dia")));
		
		out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
				"<dia:diagram xmlns:dia=\"http://www.lysator.liu.se/~alla/dia/\">" +
				"  <dia:diagramdata>" +
				"    <dia:attribute name=\"background\">" +
				"      <dia:color val=\"#aaaaaa\"/>" +
				"    </dia:attribute>" +
				"    <dia:attribute name=\"pagebreak\">" +
				"      <dia:color val=\"#000099\"/>" +
				"    </dia:attribute>" +
				"    <dia:attribute name=\"paper\">" +
				"      <dia:composite type=\"paper\">" +
				"        <dia:attribute name=\"name\">" +
				"          <dia:string>#A4#</dia:string>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"tmargin\">" +
				"          <dia:real val=\"2.8222000598907471\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"bmargin\">" +
				"          <dia:real val=\"2.8222000598907471\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"lmargin\">" +
				"          <dia:real val=\"2.8222000598907471\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"rmargin\">" +
				"          <dia:real val=\"2.8222000598907471\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"is_portrait\">" +
				"          <dia:boolean val=\"true\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"scaling\">" +
				"          <dia:real val=\"1\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"fitto\">" +
				"          <dia:boolean val=\"false\"/>" +
				"        </dia:attribute>" +
				"      </dia:composite>" +
				"    </dia:attribute>" +
				"    <dia:attribute name=\"grid\">" +
				"      <dia:composite type=\"grid\">" +
				"        <dia:attribute name=\"width_x\">" +
				"          <dia:real val=\"1\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"width_y\">" +
				"          <dia:real val=\"1\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"visible_x\">" +
				"          <dia:int val=\"1\"/>" +
				"        </dia:attribute>" +
				"        <dia:attribute name=\"visible_y\">" +
				"          <dia:int val=\"1\"/>" +
				"        </dia:attribute>" +
				"        <dia:composite type=\"color\"/>" +
				"      </dia:composite>" +
				"    </dia:attribute>" +
				"    <dia:attribute name=\"color\">" +
				"      <dia:color val=\"#d8e5e5\"/>" +
				"    </dia:attribute>" +
				"    <dia:attribute name=\"guides\">" +
				"      <dia:composite type=\"guides\">" +
				"        <dia:attribute name=\"hguides\"/>" +
				"        <dia:attribute name=\"vguides\"/>" +
				"      </dia:composite>" +
				"    </dia:attribute>" +
				"  </dia:diagramdata>" +
				"  <dia:layer name=\"Background\" visible=\"true\">"
				);
		final float WIDTH=13f;
		final float HEIGHT=15.0f;
		int i=0;
		for(i=0;i<  this.structures.size();++i)
			{
			float x= (WIDTH*(i%5));
			float y= HEIGHT*(int)(i/5);
			Table structure = this.structures.get(i);
			out.println(
					"    <dia:object type=\"UML - Class\" version=\"0\" id=\"O"+ i+"\">" +
					"      <dia:attribute name=\"obj_pos\">" +
					"        <dia:point val=\""+x+","+y+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"obj_bb\">" +
					"        <dia:rectangle val=\""+x+","+y+";"+ (x+WIDTH)+","+(y+HEIGHT)+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"elem_corner\">" +
					"        <dia:point val=\""+x+","+y+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"elem_width\">" +
					"        <dia:real val=\""+WIDTH+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"elem_height\">" +
					"        <dia:real val=\""+ HEIGHT+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"name\">" + 
					"        <dia:string>#"+ URLEncoder.encode(structure.getName(),"UTF-8")+" #</dia:string>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"stereotype\">" +
					"        <dia:string>##</dia:string>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"comment\">" +
					"        <dia:string>#"+ URLEncoder.encode(structure.getName(),"UTF-8")+" #</dia:string>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"abstract\">" +
					"        <dia:boolean val=\"false\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"suppress_attributes\">" +
					"        <dia:boolean val=\"false\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"suppress_operations\">" +
					"        <dia:boolean val=\"false\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"visible_attributes\">" +
					"        <dia:boolean val=\"true\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"visible_operations\">" +
					"        <dia:boolean val=\"true\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"visible_comments\">" +
					"        <dia:boolean val=\"false\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"wrap_operations\">" +
					"        <dia:boolean val=\"true\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"wrap_after_char\">" +
					"        <dia:int val=\"40\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"comment_line_length\">" +
					"        <dia:int val=\"17\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"comment_tagging\">" +
					"        <dia:boolean val=\"false\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"line_color\">" +
					"        <dia:color val=\"#000000\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"fill_color\">" +
					"        <dia:color val=\"#ffffff\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"text_color\">" +
					"        <dia:color val=\"#000000\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"normal_font\">" +
					"        <dia:font family=\"monospace\" style=\"0\" name=\"Courier\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"abstract_font\">" +
					"        <dia:font family=\"monospace\" style=\"88\" name=\"Courier-BoldOblique\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"polymorphic_font\">" +
					"        <dia:font family=\"monospace\" style=\"8\" name=\"Courier-Oblique\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"classname_font\">" +
					"        <dia:font family=\"sans\" style=\"80\" name=\"Helvetica-Bold\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"abstract_classname_font\">" +
					"        <dia:font family=\"sans\" style=\"88\" name=\"Helvetica-BoldOblique\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"comment_font\">" +
					"        <dia:font family=\"sans\" style=\"8\" name=\"Helvetica-Oblique\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"normal_font_height\">" +
					"        <dia:real val=\"0.80000000000000004\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"polymorphic_font_height\">" +
					"        <dia:real val=\"0.80000000000000004\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"abstract_font_height\">" +
					"        <dia:real val=\"0.80000000000000004\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"classname_font_height\">" +
					"        <dia:real val=\"1\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"abstract_classname_font_height\">" +
					"        <dia:real val=\"1\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"comment_font_height\">" +
					"        <dia:real val=\"0.69999999999999996\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"attributes\">" 
					);
					int fieldIndex=0;
					printFieldAsDia(out,structure.getKey(),"key.");
					List<DiaHandle> handles =connections.get(structure.getKey().getDescription().trim().replaceAll("[ ]+", " "));
					if(handles==null)
						{
						handles= new ArrayList<DiaHandle>();
						connections.put(structure.getKey().getDescription().trim().replaceAll("[ ]+", " "),handles);
						}
					DiaHandle newHandle=new DiaHandle();
					newHandle.structIndex=i;
					newHandle.x=x; newHandle.width=WIDTH;
					newHandle.y=y; newHandle.height=HEIGHT;
					newHandle.fieldIndex=fieldIndex;
					handles.add(newHandle);
					
					for(Field f: structure.getFields())
						{
						++fieldIndex;
						newHandle=new DiaHandle();
						newHandle.structIndex=i;
						newHandle.x=x; newHandle.width=WIDTH;
						newHandle.y=y; newHandle.height=HEIGHT;
						newHandle.fieldIndex=fieldIndex;
						
						handles =connections.get(f.getDescription().trim().replaceAll("[ ]+", " "));
						if(handles==null)
							{
							handles= new ArrayList<DiaHandle>();
							connections.put(f.getDescription().trim().replaceAll("[ ]+", " "),handles);
							}
						handles.add(newHandle);
						printFieldAsDia(out,f,"value.");
						}
					
					out.println(
					"      </dia:attribute>"+
					"      <dia:attribute name=\"operations\"/>" +
					"      <dia:attribute name=\"template\">" +
					"        <dia:boolean val=\"false\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"templates\"/>" +
					"    </dia:object>" );
			}
		
		for(String connection: connections.keySet())
			{
			List<DiaHandle> h= connections.get(connection);
			if(h.size()<2) continue;
			for(int i1=0;i1+1< h.size();i1++)
				{
				DiaHandle h1= h.get(i1);
				for(int i2=i1+1;i2< h.size();++i2)
					{
					DiaHandle h2= h.get(i2);
					out.println(
					"    <dia:object type=\"UML - Generalization\" version=\"1\" id=\"L"+ i1+"_"+i2+"\">" +
					"      <dia:attribute name=\"obj_pos\">" +
					"        <dia:point val=\""+ Math.min(h1.x,h2.x)+","+Math.min(h1.y,h2.y)+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"obj_bb\">" +
					"        <dia:rectangle val=\""+ Math.min(h1.x,h2.x)+","+Math.min(h1.y,h2.y)+";"+Math.max(h1.x,h2.x)+","+Math.max(h1.y,h2.y)+"\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"orth_points\">" +
					"        <dia:point val=\"21.395,9.7\"/>" +
					"        <dia:point val=\"22.395,9.7\"/>" +
					"        <dia:point val=\"22.395,4.7\"/>" +
					"        <dia:point val=\"13.145,4.7\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"orth_orient\">" +
					"        <dia:enum val=\"0\"/>" +
					"        <dia:enum val=\"1\"/>" +
					"        <dia:enum val=\"0\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"orth_autoroute\">" +
					"        <dia:boolean val=\"true\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"text_colour\">" +
					"        <dia:color val=\"#000000\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"line_colour\">" +
					"        <dia:color val=\"#000000\"/>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"name\">" +
					"        <dia:string>##</dia:string>" +
					"      </dia:attribute>" +
					"      <dia:attribute name=\"stereotype\">" +
					"        <dia:string>##</dia:string>" +
					"      </dia:attribute>" +
					"      <dia:connections>" +
					"        <dia:connection handle=\"0\" to=\"O"+h1.structIndex+"\" connection=\""+(8+h1.fieldIndex*2)+"\"/>" +
					"        <dia:connection handle=\"1\" to=\"O"+h2.structIndex+"\" connection=\""+(8+h2.fieldIndex*2)+"\"/>" +
					"      </dia:connections>" +
					"    </dia:object>"
					);

					}
				}
			}
		
		
		out.println("</dia:layer></dia:diagram>" );
		out.flush();
		out.close();
		}
	
	/** one-shot , generate the basis for a XML schema */
	private void generateSchema() throws IOException
		{
		PrintStream out= new PrintStream(new File("/home/lindenb/operon-schema.xml"));//TODO change this ! :-)
		out.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
		out.println("<?xml-stylesheet type='text/xsl' href='operon-schema.xsl'?>");
		out.println("<!DOCTYPE op:schema [");
		out.println("<!ENTITY rdf \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">");
		out.println("<!ENTITY owl \"http://www.w3.org/2002/07/owl#\">");
		out.println("<!ENTITY rdfs \"http://www.w3.org/2000/01/rdf-schema#\">");
		out.println("<!ENTITY op \"urn:operon:cephb.fr/schema/\">");
		out.println("<!ENTITY dc \"http://purl.org/dc/elements/1.1/\">");
		out.println("<!ENTITY xsd \"http://www.w3.org/2001/XMLSchema\">");
		out.println("]>");

	
		out.println("<op:schema");
		out.println("\txmlns:rdf=\"&rdf;\"");
		out.println("\txmlns:owl=\"&owl;\"");
		out.println("\txmlns:rdfs=\"&rdfs;\"");
		out.println("\txmlns:dc=\"&dc;\"");
		out.println("\txmlns:xsd=\"&xsd;\"");
		out.println("\txmlns:op=\"&op;\"");
		out.println(">");
		
		out.println(
		"<op:SimpleType id=\"char*\">\n" +
		" <dc:title xml:lang=\"en\">String</dc:title>\n" +
		" <dc:description xml:lang=\"en\">0 ended C String</dc:description>\n" +
		" <op:type language=\"C\">char*</op:type>\n" +
		" <op:type language=\"Java\">java.lang.String</op:type>\n" +
		"</op:SimpleType>\n" +
		"\n" +
		"<op:SimpleType id=\"fid\">\n" +
		" <dc:title xml:lang=\"en\">Fid</dc:title>\n" +
		" <dc:description xml:lang=\"en\">Feature map ID (15-digit string)</dc:description>\n" +
		" <op:type language=\"C\">char*</op:type>\n" +
		" <op:type language=\"Java\">org.cephb.operon.core.Fid</op:type>\n" +
		"</op:SimpleType>\n" 
		);
		
		
		for(Organism o:new Organism[]{Organism.Homo_sapiens,Organism.Mus_musculus})
			{
			out.println("");
			out.println("<op:Organism id=\"taxon-id"+o.getOperonId()+"\" >");
			out.println(" <dc:title>"+XMLUtilities.escape(o.getName())+"</dc:title>");
			out.println(" <op:taxon-id>"+o.getTaxId()+"</op:taxon-id>");
			out.println(" <op:operon-id>"+o.getOperonId()+"</op:operon-id>");
			out.println(" <op:tree-letter-code>"+o.getTreeLetterCode()+"</op:tree-letter-code>");
			out.println("</op:Organism>");
			}
	
		for(Assembly as:Assembly.getAssemblies())
			{
			out.println("");
			out.println("<op:Assembly id=\"assembly-id"+as.getId()+"\">");
			out.println(" <dc:title xml:lang=\"en\">"+XMLUtilities.escape(as.getName())+"</dc:title>");
			out.println(" <dc:description xml:lang=\"en\">"+XMLUtilities.escape(as.getName())+"</dc:description>");
			out.println(" <op:organism ref='taxon-id"+ as.getOrganism().getOperonId() +"'/>");
			out.println(" <op:short-code>"+as.getShortCode()+"</op:short-code>");
			out.println(" <op:operon-id>"+as.getId()+"</op:operon-id>");
			out.println(" <op:source>"+as.getSource()+"</op:source>");
			out.println(" <op:version>"+as.getVersion()+"</op:version>");
			out.println("</op:Assembly>");
			}
			
		for(Table struct: this.structures)
			{
			out.println("");
			out.println("<op:ComplexType id=\""+struct.getName()+"\" fixed-size=\""+ struct.hasFixedSize()+"\">");
			out.println(" <dc:title xml:lang=\"en\">"+XMLUtilities.escape(struct.getName())+"</dc:title>");
			out.println(" <dc:description xml:lang=\"en\">"+XMLUtilities.escape(struct.getDescription())+"</dc:description>");
			out.println(" <dc:creator>Mario Foglio</dc:creator>");
			out.println(" <dc:date>2009-07-08</dc:date>");
			out.println(" <op:fields>");
			for(Field f:struct.getFields())
				{
				out.println("   <op:Field name=\""+f.getName()+"\">");
				out.println("     <dc:title xml:lang=\"en\">"+XMLUtilities.escape(f.getName())+"</dc:title>");
				out.println("     <dc:description xml:lang=\"en\">"+XMLUtilities.escape(f.getDescription())+"</dc:description>");
				out.println("     <op:expect-size>"+f.expectedSize+"</op:expect-size>");
				if(struct.getKey().isFid())
					{
					out.println("   <op:type ref=\"fid\"/>");
					}
				else
					{
					out.println("   <op:type ref=\""+ f.getType()+"\"/>");
					}
				out.println("   </op:Field>");
				}
			out.println(" </op:fields>");
			out.println("</op:ComplexType>");
			}
		
		
		for(Table struct: this.filteredListOfStructures())
			{
			out.println("");
			out.println("<op:Database id=\""+struct.getName()+"DB\">");
			out.println(" <dc:title xml:lang=\"en\">"+XMLUtilities.escape(struct.getName())+"</dc:title>");
			out.println(" <dc:description xml:lang=\"en\">"+XMLUtilities.escape(struct.getDescription())+"</dc:description>");
			out.println(" <dc:creator>Mario Foglio</dc:creator>");
			out.println(" <dc:date>2009-07-08</dc:date>");
			out.println(" <op:domain>"+struct.getDomain()+"</op:domain>");
			out.println(" <op:database-type>"+struct.getDatabaseType()+"</op:database-type>");
			out.println(" <op:assembly-dependant>"+struct.isAssemblyDependent()+"</op:assembly-dependant>");
			out.println(" <op:allow-duplicate>"+struct.isAllowingDuplicates()+"</op:allow-duplicate>");
			out.println(" <op:in-memory>"+struct.isInMemory()+"</op:in-memory>");
			out.println(" <op:key-type duplicate='"+struct.isAllowingDuplicates()+"'>");
			out.println("     <op:expect-size>"+struct.getKey().expectedSize+"</op:expect-size>");
			if(struct.getKey().isFid())
				{
				out.println("   <op:type ref=\"fid\"/>");
				}
			else
				{
				out.println("   <op:type ref=\""+ struct.getKey().getType()+"\"/>");
				}
			out.println("   <dc:title>"+XMLUtilities.escape(struct.getKey().getName())+"</dc:title>");
			out.println("   <dc:description>"+XMLUtilities.escape(struct.getKey().getDescription())+"</dc:description>");
			out.println(" </op:key-type>");
			out.println(" <op:value-type>");
			out.println("   <op:type ref=\""+ struct.getName()+"\"/>");
			out.println(" </op:value-type>");
			for(SecondaryDB sec:struct.getSecondary())
				{
				out.println(" <op:secondary-db>");
				out.println("   <op:SecondaryDatabase on-field-name=\""+ sec.getKey().getName()+"\">");
				out.println("     <dc:title xml:lang=\"en\">"+XMLUtilities.escape(sec.getName())+"</dc:title>");
				out.println("     <dc:description xml:lang=\"en\">"+XMLUtilities.escape(sec.getDescription())+"</dc:description>");
				out.println("   </op:SecondaryDatabase>");
				out.println(" </op:secondary-db>");
				}
			out.println("</op:Database>");
			}
		out.println("\n"+
			"<op:DatabaseInstance id=\"IntanceOfSnpIdsDB\" database-ref='SnpIdsDB' path=\"dbs/hsa/v36/ref/hsa_ref_snpids_36.bdb\" assembly-id=\"assembly-id1\">\n" +
			" <dc:title xml:lang=\"en\">SnpIds</dc:title>\n" +
			" <dc:description xml:lang=\"en\">SNP identifiers</dc:description>\n" +
			" <dc:creator>Mario Foglio</dc:creator>\n" +
			" <dc:date>2009-07-08</dc:date>\n" +
			"</op:DatabaseInstance>\n" 
			);
		out.println("</op:schema>");
		out.flush();
		out.close();
		}
	
	public static void main(String[] args) {
		try {
			XLS2CodeGeneration app= new XLS2CodeGeneration();
			File inputFile=null;
			int optind=0;
		    while(optind<args.length)
				{
				if(args[optind].equals("-h"))
					{
					System.err.println("Pierre Lindenbaum PhD.");
					System.err.println("-h this screen");
					System.err.println("-J Mario's excel file schema");
					return;
					}
				 else if (args[optind].equals("-J"))
				     {
					 inputFile=new File(args[++optind]);
				     }
				 else if (args[optind].equals("--"))
				     {
				     ++optind;
				     break;
				     }
				else if (args[optind].startsWith("-"))
				     {
				     System.err.println("bad argument " + args[optind]);
				     System.exit(-1);
				     }
				else
				     {
				     break;
				     }
				++optind;
				}
		    if(inputFile==null)
		    	{
		    	System.err.println("Input missing.");
		    	return;
		    	}
		    
		    app.read(inputFile);
		    
		    //app.generateSchema();
		    //if(1==1) return;
		    
		    app.removeWeirdTables();
		    
		    app.generateCPPCode();
		    app.generatePythonCode();
		    app.generateJavaCode();
		    app.generateCCode();
		    //app.generateDia();
		    app.generateDot();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}